package com.hexb.core.exception

import com.hexb.core.common.ResponseResult
import org.apache.shiro.authc.DisabledAccountException
import org.apache.shiro.authc.IncorrectCredentialsException
import org.apache.shiro.authz.AuthorizationException
import org.apache.shiro.authz.UnauthenticatedException
import org.apache.shiro.authz.UnauthorizedException
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.context.MessageSource
import org.springframework.context.i18n.LocaleContextHolder
import org.springframework.http.HttpStatus
import org.springframework.web.bind.MethodArgumentNotValidException
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestControllerAdvice

import java.text.MessageFormat

/**
 * @author:hexb
 * @Date: 2017-12-2017
 */

@RestControllerAdvice
@ConditionalOnProperty(value = 'project.exceptionAdvice', havingValue = 'true')
class ExceptionAdvice {
    private static final Logger logger = LoggerFactory.getLogger(ExceptionAdvice.class)

    @Autowired
    private MessageSource messageSource;

    @ExceptionHandler(value = IllegalArgumentException.class)
    @ResponseStatus(value = HttpStatus.OK)
    ResponseResult handlingIllegalArgumentException(IllegalArgumentException e) {
        def tuple = extractCodeAndMessage(e.message)
        ResponseResult.unsuccessful(tuple.getFirst() ?: 'illegal_argument', tuple.getSecond())
    }

    @ExceptionHandler(value = IllegalStateException.class)
    @ResponseStatus(value = HttpStatus.OK)
    ResponseResult handlingIllegalArgumentException(IllegalStateException e) {
        def tuple = extractCodeAndMessage(e.message)
        ResponseResult.unsuccessful(tuple.getFirst() ?: 'illegal_state', tuple.getSecond())
    }

    //授权失败异常
    @ExceptionHandler(value = BusinessUnauthorizedException.class)
    @ResponseStatus(value = HttpStatus.FORBIDDEN)
    ResponseResult handlingException(BusinessUnauthorizedException e) {
        getI18nResponseResult(e)
    }


    @ExceptionHandler(value = BusinessException.class)
    @ResponseStatus(value = HttpStatus.OK)
    ResponseResult handlerBusinessException(BusinessException e) {
        getI18nResponseResult(e)
    }


    @ExceptionHandler(value = RuntimeException.class)
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    ResponseResult handlingRuntimeException(RuntimeException e) {
        logger.error('catch error ==>', e)
        ResponseResult.unsuccessful(HttpStatus.INTERNAL_SERVER_ERROR.toString(), "发生错误,类型:${e.class.simpleName}")
    }


    @ExceptionHandler(value = GroovyRuntimeException.class)
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    ResponseResult handlingGroovyRuntimeException(GroovyRuntimeException e) {
        logger.error('catch error ==>', e)
        ResponseResult.unsuccessful(HttpStatus.INTERNAL_SERVER_ERROR.toString(), "发生错误,类型:${e.class.simpleName}")
    }


    @ExceptionHandler(value = Exception.class)
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    ResponseResult handlingException(Exception e) {
        logger.error('catch error ==>', e)
        ResponseResult.unsuccessful(HttpStatus.INTERNAL_SERVER_ERROR.toString(), "发生错误,类型:${e.class.simpleName}")
    }

    /*** shiro 相关异常 **/

    @ExceptionHandler(value = UnauthenticatedException.class)
    @ResponseStatus(value = HttpStatus.UNAUTHORIZED)
    ResponseResult handlingUnauthenticatedException() {
        logger.error('catch error ==> UnauthenticatedException')
        getI18nResponseResult("unauthorized_no_login", "抱歉，您还未登录，请先登录！")
    }

    @ExceptionHandler(value = AuthorizationException.class)
    @ResponseStatus(value = HttpStatus.FORBIDDEN)
    ResponseResult handlingAuthorizationException() {
        logger.error('catch error ==> AuthorizationException')
        getI18nResponseResult("authorization_no_login", "该资源需要登录用户才能访问")
    }


    @ExceptionHandler(value = UnauthorizedException.class)
    @ResponseStatus(value = HttpStatus.FORBIDDEN)
    ResponseResult handlingUnauthorizedException() {
        logger.error('catch error ==> UnauthorizedException')
        getI18nResponseResult("unauthorized", "没有访问该资源的权限")
    }


    @ExceptionHandler(value = IncorrectCredentialsException.class)
    @ResponseStatus(value = HttpStatus.OK)
    ResponseResult handlingIncorrectCredentialsException() {
        logger.error('catch error ==> IncorrectCredentialsException')
        getI18nResponseResult("incorrect_credentials", "登录失败，登录名或密码错误")
    }


    @ExceptionHandler(value = DisabledAccountException.class)
    @ResponseStatus(value = HttpStatus.OK)
    ResponseResult handlingAuthenticationException(DisabledAccountException e) {
        logger.error('catch error ==> DisabledAccountException')
        getI18nResponseResult("disabled_account", "登录失败，该帐号被禁用", e)
    }

    /** 参数异常 */

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    @ResponseStatus(value = HttpStatus.OK)
    ResponseResult handlingMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        logger.error('catch error ==> MethodArgumentNotValidException')
        def details = e.getBindingResult().allErrors.collect {
            new ValidationMessage(field: it['field'], message: getValidationMessage(it.defaultMessage))
        }
        def result = getI18nResponseResult("illegal_argument", "不合法的参数", e)
        result.data = details
        result
    }

    @ExceptionHandler(value = InvalidValueOfEnumException.class)
    @ResponseStatus(value = HttpStatus.OK)
    ResponseResult handlingMethodArgumentNotValidException(InvalidValueOfEnumException e) {
        logger.error('catch error ==> InvalidValueOfEnumException')
        return getI18nResponseResult("invalid_enum_value", "不合法的枚举值")

    }

    private String getValidationMessage(String message) {
        def regex = ~'^\\{(.*)\\}$'
        if (message.matches(regex)) {
            def matcher = message =~ regex
            return getMessage(matcher[0][1], message)
        }
        return message
    }

    private ResponseResult getI18nResponseResult(String key, String defaultMessages, Exception e) {
        return ResponseResult.unsuccessful(e, key, getMessage(key, defaultMessages))
    }

    private ResponseResult getI18nResponseResult(String key, String defaultMessages) {
        return ResponseResult.unsuccessful(key, getMessage(key, defaultMessages))
    }

    private ResponseResult getI18nResponseResult(I18Nable e) {
        def result = ResponseResult.unsuccessful(e)
        if (messageSource && e.getKey()) {
            Locale locale = LocaleContextHolder.getLocale()
            logger.debug(locale.getDisplayName())
            try {
                String message = messageSource.getMessage(e.getKey(), e.getArgs(), locale)
                if (message) {
                    result.message = message
                    return result
                }
            } catch (Exception ex) {
                logger.debug(ex.message)
            }
        }
        result.message = MessageFormat.format(e.getMessage(), e.getArgs() as Object[])
        result
    }

    private String getMessage(String key, String defaultMessage, String... args) {
        Locale locale = LocaleContextHolder.getLocale()
        try {
            String message = messageSource.getMessage(key, args, locale)
            if (message) {
                return message
            }
        } catch (Exception ex) {
            logger.debug(ex.message)
        }
        return MessageFormat.format(defaultMessage, args)
    }

    private extractCodeAndMessage = { String s ->
        def regex = ~'^(.*?)\\|(.*?)$'
        def matcher = s =~ regex
        String code, message
        if (matcher && matcher[0]) {
            if (matcher[0][1]) {
                code = matcher[0][1]
            }
            if (matcher[0][2]) {
                message = matcher[0][2]
            }
            return new Tuple2<String, String>(code, message)
        }
        return new Tuple2<>(null, s)
    }

    static class ValidationMessage {
        String field
        String message
    }

}
